/**
 * @author       Mat Groves http://matgroves.com @Doormat23
 * @author       Richard Davey <rich@photonstorm.com>
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * The base class for all objects that are rendered. Contains properties for position, scaling,
 * rotation, masks and cache handling.
 *
 * This is an abstract class and should not be used on its own, rather it should be extended.
 *
 * It is used internally by the likes of PIXI.Sprite.
 *
 * @class PIXI.DisplayObject
 * @constructor
 */
PIXI.DisplayObject = function ()
{
    /**
     * The coordinates, in pixels, of this DisplayObject, relative to its parent container.
     *
     * The value of this property does not reflect any positioning happening further up the display list.
     * To obtain that value please see the `worldPosition` property.
     *
     * @property {PIXI.Point} position
     * @default
     */
    this.position = new PIXI.Point(0, 0);

    /**
     * The scale of this DisplayObject. A scale of 1:1 represents the DisplayObject
     * at its default size. A value of 0.5 would scale this DisplayObject by half, and so on.
     *
     * The value of this property does not reflect any scaling happening further up the display list.
     * To obtain that value please see the `worldScale` property.
     *
     * @property {PIXI.Point} scale
     * @default
     */
    this.scale = new PIXI.Point(1, 1);

    /**
     * The pivot point of this DisplayObject that it rotates around. The values are expressed
     * in pixel values.
     * @property {PIXI.Point} pivot
     * @default
     */
    this.pivot = new PIXI.Point(0, 0);

    /**
     * The rotation of this DisplayObject. The value is given, and expressed, in radians, and is based on
     * a right-handed orientation.
     *
     * The value of this property does not reflect any rotation happening further up the display list.
     * To obtain that value please see the `worldRotation` property.
     *
     * @property {number} rotation
     * @default
     */
    this.rotation = 0;

    /**
     * The alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent.
     * Please note that an object with an alpha value of 0 is skipped during the render pass.
     *
     * The value of this property does not reflect any alpha values set further up the display list.
     * To obtain that value please see the `worldAlpha` property.
     *
     * @property {number} alpha
     * @default
     */
    this.alpha = 1;

    /**
     * The visibility of this DisplayObject. A value of `false` makes the object invisible.
     * A value of `true` makes it visible.
     *
     * An object with a visible value of `false` is skipped during the render pass.
     * Equally a DisplayObject with visible `false` will not render any of its children.
     *
     * The value of this property does not reflect any visible values set further up the display list.
     * To obtain that value please see the {@link #worldVisible} property.
     *
     * Objects that are not {@link #worldVisible} do not update their {@link #worldPosition}.
     *
     * @property {boolean} visible
     * @default
     */
    this.visible = true;

    /**
     * This is the defined area that will pick up mouse / touch events. It is null by default.
     * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children)
     *
     * @property hitArea
     * @type Rectangle|Circle|Ellipse|Polygon
     */
    this.hitArea = null;

    /**
     * Should this DisplayObject be rendered by the renderer? An object with a renderable value of
     * `false` is skipped during the render pass.
     *
     * @property {boolean} renderable
     * @default
     */
    this.renderable = false;

    /**
     * The parent DisplayObjectContainer that this DisplayObject is a child of.
     * All DisplayObjects must belong to a parent in order to be rendered.
     * The root parent is the Stage object. This property is set automatically when the
     * DisplayObject is added to, or removed from, a DisplayObjectContainer.
     *
     * @property {PIXI.DisplayObjectContainer} parent
     * @default
     * @readOnly
     */
    this.parent = null;

    /**
     * The multiplied alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent.
     * This value is the calculated total, based on the alpha values of all parents of this DisplayObjects
     * in the display list.
     *
     * To obtain, and set, the local alpha value, see the `alpha` property.
     *
     * Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
     * that happens this property will contain values based on the previous frame. Be mindful of this if
     * accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
     *
     * @property {number} worldAlpha
     * @readOnly
     */
    this.worldAlpha = 1;

    /**
     * The current transform of this DisplayObject.
     *
     * This property contains the calculated total, based on the transforms of all parents of this
     * DisplayObject in the display list.
     *
     * Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
     * that happens this property will contain values based on the previous frame. Be mindful of this if
     * accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
     *
     * @property {Phaser.Matrix} worldTransform
     * @readOnly
     */
    this.worldTransform = new Phaser.Matrix();

    /**
     * The coordinates, in pixels, of this DisplayObject within the world.
     *
     * This property contains the calculated total, based on the positions of all parents of this
     * DisplayObject in the display list.
     *
     * Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
     * that happens this property will contain values based on the previous frame. Be mindful of this if
     * accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
     *
     * @property {PIXI.Point} worldPosition
     * @readOnly
     */
    this.worldPosition = new PIXI.Point(0, 0);

    /**
     * The global scale of this DisplayObject.
     *
     * This property contains the calculated total, based on the scales of all parents of this
     * DisplayObject in the display list.
     *
     * Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
     * that happens this property will contain values based on the previous frame. Be mindful of this if
     * accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
     *
     * @property {PIXI.Point} worldScale
     * @readOnly
     */
    this.worldScale = new PIXI.Point(1, 1);

    /**
     * The rotation, in radians, of this DisplayObject.
     *
     * This property contains the calculated total, based on the rotations of all parents of this
     * DisplayObject in the display list.
     *
     * Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
     * that happens this property will contain values based on the previous frame. Be mindful of this if
     * accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
     *
     * @property {number} worldRotation
     * @readOnly
     */
    this.worldRotation = 0;

    /**
     * The rectangular area used by filters when rendering a shader for this DisplayObject.
     *
     * @property {PIXI.Rectangle} filterArea
     * @type Rectangle
     * @default
     */
    this.filterArea = null;

    /**
     * @property {number} _sr - Cached rotation value.
     * @private
     */
    this._sr = 0;

    /**
     * @property {number} _cr - Cached rotation value.
     * @private
     */
    this._cr = 1;

    /**
     * @property {PIXI.Rectangle} _bounds - The cached bounds of this object.
     * @private
     */
    this._bounds = new PIXI.Rectangle(0, 0, 0, 0);

    /**
     * @property {PIXI.Rectangle} _currentBounds - The most recently calculated bounds of this object.
     * @private
     */
    this._currentBounds = null;

    /**
     * @property {PIXI.Rectangle} _mask - The cached mask of this object.
     * @private
     */
    this._mask = null;

    /**
     * @property {boolean} _cacheAsBitmap - Internal cache as bitmap flag.
     * @private
     */
    this._cacheAsBitmap = false;

    /**
     * @property {boolean} _cacheIsDirty - Internal dirty cache flag.
     * @private
     */
    this._cacheIsDirty = false;
};

PIXI.DisplayObject.prototype = {

    constructor: PIXI.DisplayObject,

    /**
     * Destroy this DisplayObject.
     *
     * Removes any cached sprites, sets renderable flag to false, and nulls filters, bounds and mask.
     *
     * Also iteratively calls `destroy` on any children.
     *
     * @method PIXI.DisplayObject#destroy
     */
    destroy: function ()
    {
        if (this.children)
        {
            var i = this.children.length;

            while (i--)
            {
                this.children[i].destroy();
            }

            this.children = [];
        }

        this.hitArea = null;
        this.parent = null;
        this.worldTransform = null;
        this.filterArea = null;
        this.renderable = false;

        this._bounds = null;
        this._currentBounds = null;
        this._mask = null;

        this._destroyCachedSprite();
        this._destroyTintedTexture();
    },

    /**
     * Updates the transform matrix this DisplayObject uses for rendering.
     *
     * If the object has no parent, and no parent parameter is provided, it will default to
     * Phaser.Game.World as the parent transform to use. If that is unavailable the transform fails to take place.
     *
     * The `parent` parameter has priority over the actual parent. Use it as a parent override.
     * Setting it does **not** change the actual parent of this DisplayObject.
     *
     * Calling this method updates the `worldTransform`, `worldAlpha`, `worldPosition`, `worldScale`
     * and `worldRotation` properties.
     *
     * If a `transformCallback` has been specified, it is called at the end of this method, and is passed
     * the new, updated, worldTransform property, along with the parent transform used.
     *
     * @method PIXI.DisplayObject#updateTransform
     * @param {PIXI.DisplayObjectContainer} [parent] - Optional parent to calculate this DisplayObjects transform from.
     * @return {PIXI.DisplayObject} - A reference to this DisplayObject.
     */
    updateTransform: function (parent)
    {
        if (!parent && !this.parent && !this.game)
        {
            return this;
        }

        var p = this.parent;

        if (parent)
        {
            p = parent;
        }
        else if (!this.parent)
        {
            p = this.game.world;
        }

        // create some matrix refs for easy access
        var pt = p.worldTransform;
        var wt = this.worldTransform;

        // temporary matrix variables
        var a, b, c, d, tx, ty;

        // so if rotation is between 0 then we can simplify the multiplication process..
        if (this.rotation % Phaser.Math.PI2)
        {
            // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes
            if (this.rotation !== this.rotationCache)
            {
                this.rotationCache = this.rotation;
                this._sr = Math.sin(this.rotation);
                this._cr = Math.cos(this.rotation);
            }

            // get the matrix values of the displayobject based on its transform properties..
            a = this._cr * this.scale.x;
            b = this._sr * this.scale.x;
            c = -this._sr * this.scale.y;
            d = this._cr * this.scale.y;
            tx = this.position.x;
            ty = this.position.y;

            // check for pivot.. not often used so geared towards that fact!
            if (this.pivot.x || this.pivot.y)
            {
                tx -= this.pivot.x * a + this.pivot.y * c;
                ty -= this.pivot.x * b + this.pivot.y * d;
            }

            // concat the parent matrix with the objects transform.
            wt.a = a * pt.a + b * pt.c;
            wt.b = a * pt.b + b * pt.d;
            wt.c = c * pt.a + d * pt.c;
            wt.d = c * pt.b + d * pt.d;
            wt.tx = tx * pt.a + ty * pt.c + pt.tx;
            wt.ty = tx * pt.b + ty * pt.d + pt.ty;
        }
        else
        {
            // lets do the fast version as we know there is no rotation..
            a = this.scale.x;
            b = 0;
            c = 0;
            d = this.scale.y;
            tx = this.position.x - this.pivot.x * a;
            ty = this.position.y - this.pivot.y * d;

            wt.a = a * pt.a;
            wt.b = a * pt.b;
            wt.c = d * pt.c;
            wt.d = d * pt.d;
            wt.tx = tx * pt.a + ty * pt.c + pt.tx;
            wt.ty = tx * pt.b + ty * pt.d + pt.ty;
        }

        a = wt.a;
        b = wt.b;
        c = wt.c;
        d = wt.d;

        var determ = (a * d) - (b * c);

        if (a || b)
        {
            var r = Math.sqrt((a * a) + (b * b));

            this.worldRotation = (b > 0) ? Math.acos(a / r) : -Math.acos(a / r);
            this.worldScale.x = r;
            this.worldScale.y = determ / r;
        }
        else if (c || d)
        {
            var s = Math.sqrt((c * c) + (d * d));

            this.worldRotation = Phaser.Math.HALF_PI - ((d > 0) ? Math.acos(-c / s) : -Math.acos(c / s));
            this.worldScale.x = determ / s;
            this.worldScale.y = s;
        }
        else
        {
            this.worldScale.x = 0;
            this.worldScale.y = 0;
        }

        //  Set the World values
        this.worldAlpha = this.alpha * p.worldAlpha;
        this.worldPosition.x = wt.tx;
        this.worldPosition.y = wt.ty;

        // reset the bounds each time this is called!
        this._currentBounds = null;

        //  Custom callback?
        if (this.transformCallback)
        {
            this.transformCallback.call(this.transformCallbackContext, wt, pt);
        }

        return this;
    },

    /**
     * To be overridden by classes that require it.
     *
     * @method PIXI.DisplayObject#preUpdate
     */
    preUpdate: function ()
    {

    },

    /**
     * Generates a RenderTexture based on this DisplayObject, which can they be used to texture other Sprites.
     * This can be useful if your DisplayObject is static, or complicated, and needs to be reused multiple times.
     *
     * Please note that no garbage collection takes place on old textures. It is up to you to destroy old textures,
     * and references to them, so they don't linger in memory.
     *
     * @method PIXI.DisplayObject#generateTexture
     * @param {number} [resolution=1] - The resolution of the texture being generated.
     * @param {number} [scaleMode=PIXI.scaleModes.DEFAULT] - See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values.
     * @param {PIXI.CanvasRenderer|PIXI.WebGLRenderer} renderer - The renderer used to generate the texture.
     * @return {Phaser.RenderTexture} - A RenderTexture containing an image of this DisplayObject at the time it was invoked.
     */
    generateTexture: function (resolution, scaleMode, renderer)
    {
        var bounds = this.getLocalBounds();

        var renderTexture = new Phaser.RenderTexture(this.game, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution);

        PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
        PIXI.DisplayObject._tempMatrix.ty = -bounds.y;

        renderTexture.render(this, PIXI.DisplayObject._tempMatrix);

        return renderTexture;
    },

    /**
     * If this DisplayObject has a cached Sprite, this method generates and updates it.
     *
     * @method PIXI.DisplayObject#updateCache
     * @return {PIXI.DisplayObject} - A reference to this DisplayObject.
     */
    updateCache: function ()
    {
        this._generateCachedSprite();

        return this;
    },

    /**
     * Calculates the global position of this DisplayObject, based on the position given.
     *
     * @method PIXI.DisplayObject#toGlobal
     * @param {PIXI.Point} position - The global position to calculate from.
     * @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given.
     */
    toGlobal: function (position)
    {
        this.updateTransform();

        return this.worldTransform.apply(position);
    },

    /**
     * Calculates the local position of this DisplayObject, relative to another point.
     *
     * @method PIXI.DisplayObject#toLocal
     * @param {PIXI.Point} position - The world origin to calculate from.
     * @param {PIXI.DisplayObject} [from] - An optional DisplayObject to calculate the global position from.
     * @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given.
     */
    toLocal: function (position, from)
    {
        if (from)
        {
            position = from.toGlobal(position);
        }

        this.updateTransform();

        return this.worldTransform.applyInverse(position);
    },

    /**
     * Internal method.
     *
     * @method PIXI.DisplayObject#_renderCachedSprite
     * @private
     * @param {Object} renderSession - The render session
     */
    _renderCachedSprite: function (renderSession)
    {
        this._cachedSprite.worldAlpha = this.worldAlpha;

        if (renderSession.gl)
        {
            PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession);
        }
        else
        {
            PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession);
        }
    },

    /**
     * Internal method.
     *
     * @method PIXI.DisplayObject#_generateCachedSprite
     * @private
     */
    _generateCachedSprite: function ()
    {
        this._cacheAsBitmap = false;

        var bounds = this.getLocalBounds();

        //  Round it off and force non-zero dimensions
        bounds.width = Math.max(1, Math.ceil(bounds.width));
        bounds.height = Math.max(1, Math.ceil(bounds.height));

        this.updateTransform();

        if (!this._cachedSprite)
        {
            var textureUnit = 0;
            if (this.texture && this.texture.baseTexture && PIXI._enableMultiTextureToggle)
            {
                textureUnit = this.texture.baseTexture.textureIndex;
            }
            var renderTexture = new Phaser.RenderTexture(this.game, bounds.width, bounds.height, undefined, undefined, undefined, undefined, textureUnit);
            this._cachedSprite = new PIXI.Sprite(renderTexture);
            this._cachedSprite.worldTransform = this.worldTransform;
        }
        else
        {
            this._cachedSprite.texture.resize(bounds.width, bounds.height);
        }

        //  Remove filters
        var tempFilters = this._filters;

        this._filters = null;
        this._cachedSprite.filters = tempFilters;

        PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
        PIXI.DisplayObject._tempMatrix.ty = -bounds.y;
        this._cachedSprite.texture.render(this, PIXI.DisplayObject._tempMatrix, true);
        this._cachedSprite.anchor.x = -(bounds.x / bounds.width);
        this._cachedSprite.anchor.y = -(bounds.y / bounds.height);

        this._filters = tempFilters;

        this._cacheAsBitmap = true;
    },

    /**
     * Destroys a cached Sprite.
     *
     * @method PIXI.DisplayObject#_destroyCachedSprite
     * @private
     */
    _destroyCachedSprite: function ()
    {
        if (!this._cachedSprite)
        {
            return;
        }

        this._cachedSprite.texture.destroy(true);

        this._cachedSprite = null;
    },

    _destroyTintedTexture: function ()
    {
        if (!this.tintedTexture)
        {
            return;
        }

        Phaser.CanvasPool.removeByCanvas(this.tintedTexture);

        this.tintedTexture = null;
    }

};

//  Alias for updateTransform. As used in DisplayObject container, etc.
PIXI.DisplayObject.prototype.displayObjectUpdateTransform = PIXI.DisplayObject.prototype.updateTransform;

Object.defineProperties(PIXI.DisplayObject.prototype, {

    /**
     * The horizontal position of the DisplayObject, in pixels, relative to its parent.
     * If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead.
     * @name PIXI.DisplayObject#x
     * @property {number} x - The horizontal position of the DisplayObject, in pixels, relative to its parent.
     */
    x: {

        get: function ()
        {
            return this.position.x;
        },

        set: function (value)
        {
            this.position.x = value;
        }

    },

    /**
     * The vertical position of the DisplayObject, in pixels, relative to its parent.
     * If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead.
     * @name PIXI.DisplayObject#y
     * @property {number} y - The vertical position of the DisplayObject, in pixels, relative to its parent.
     */
    y: {

        get: function ()
        {
            return this.position.y;
        },

        set: function (value)
        {
            this.position.y = value;
        }

    },

    /**
     * Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values.
     * @name PIXI.DisplayObject#worldVisible
     * @property {boolean} worldVisible - Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values.
     */
    worldVisible: {

        get: function ()
        {
            if (!this.visible)
            {
                return false;
            }
            else
            {
                var item = this.parent;

                if (!item)
                {
                    return this.visible;
                }
                else
                {
                    do
                    {
                        if (!item.visible)
                        {
                            return false;
                        }

                        item = item.parent;
                    }
                    while (item);
                }

                return true;
            }
        }

    },

    /**
     * Sets a mask for this DisplayObject. A mask is an instance of a Graphics object.
     * When applied it limits the visible area of this DisplayObject to the shape of the mask.
     * Under a Canvas renderer it uses shape clipping. Under a WebGL renderer it uses a Stencil Buffer.
     * To remove a mask, set this property to `null`.
     *
     * @name PIXI.DisplayObject#mask
     * @property {Phaser.Graphics} mask - The mask applied to this DisplayObject. Set to `null` to remove an existing mask.
     */
    mask: {

        get: function ()
        {
            return this._mask;
        },

        set: function (value)
        {
            if (this._mask)
            {
                this._mask.isMask = false;
            }

            this._mask = value;

            if (value)
            {
                this._mask.isMask = true;
            }
        }

    },

    /**
     * Sets the filters for this DisplayObject. This is a WebGL only feature, and is ignored by the Canvas
     * Renderer. A filter is a shader applied to this DisplayObject. You can modify the placement of the filter
     * using `DisplayObject.filterArea`.
     *
     * To remove filters, set this property to `null`.
     *
     * Note: You cannot have a filter set, and a MULTIPLY Blend Mode active, at the same time. Setting a
     * filter will reset this DisplayObjects blend mode to NORMAL.
     *
     * @name PIXI.DisplayObject#filters
     * @property {Array} filters - An Array of Phaser.Filter objects, or objects that extend them.
     */
    filters: {

        get: function ()
        {
            return this._filters;
        },

        set: function (value)
        {
            if (Array.isArray(value))
            {
                //  Put all the passes in one place.
                var passes = [];

                for (var i = 0; i < value.length; i++)
                {
                    var filterPasses = value[i].passes;

                    for (var j = 0; j < filterPasses.length; j++)
                    {
                        passes.push(filterPasses[j]);
                    }
                }

                //  Needed any more?
                this._filterBlock = { target: this, filterPasses: passes };
            }

            this._filters = value;

            if (this.blendMode && this.blendMode === PIXI.blendModes.MULTIPLY)
            {
                this.blendMode = PIXI.blendModes.NORMAL;
            }
        }

    },

    /**
     * Sets if this DisplayObject should be cached as a bitmap.
     *
     * When invoked it will take a snapshot of the DisplayObject, as it is at that moment, and store it
     * in a RenderTexture. This is then used whenever this DisplayObject is rendered. It can provide a
     * performance benefit for complex, but static, DisplayObjects. I.e. those with lots of children.
     *
     * Transparent areas adjoining the edges may be removed ({@link https://github.com/photonstorm/phaser-ce/issues/283 #283}).
     *
     * Cached Bitmaps do not track their parents. If you update a property of this DisplayObject, it will not
     * re-generate the cached bitmap automatically. To do that you need to call `DisplayObject.updateCache`.
     *
     * To remove a cached bitmap, set this property to `null`.
     *
     * @name PIXI.DisplayObject#cacheAsBitmap
     * @property {boolean} cacheAsBitmap - Cache this DisplayObject as a Bitmap. Set to `null` to remove an existing cached bitmap.
     */
    cacheAsBitmap: {

        get: function ()
        {
            return this._cacheAsBitmap;
        },

        set: function (value)
        {
            if (this._cacheAsBitmap === value)
            {
                return;
            }

            if (value)
            {
                this._generateCachedSprite();
            }
            else
            {
                this._destroyCachedSprite();
            }

            this._cacheAsBitmap = value;
        }

    }

});
